import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * redis工具类
 * pool.returnBrokenResource 和 pool.returnResource 会报错 xxx has protected access in
 * jedis 3.0之前，使用 jedisPool.returnResource(jedis)和pool.returnBrokenResource
 * jedis 3.0之后使用jedis.close()
 * 调用close方法后会将连接归还给连接池
 */
public class RedisUtil {
    // Redis服务器IP
    private static String ADDR = "127.0.0.1";
    // Redis的端口号
    private static int PORT = 6379;
    // 访问密码
    private static String AUTH = "abc123";

    // 可用连接实例的最大数目，默认值为8；
    // 如果赋值为-1，则表示不限制；如果pool已经分配了maxActive个jedis实例，则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = 1024;
    // 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例，默认值也是8。
    private static int MAX_IDLE = 200;
    // 等待可用连接的最大时间，单位毫秒，默认值为-1，表示永不超时。
    // 如果超过等待时间，则直接抛出JedisConnectionException；
    private static int MAX_WAIT = 10000;
    // 过期时间
    protected static int  expireTime = 60 * 60 *24;

    // 连接池
    protected static JedisPool pool;

    /**
     * 静态代码，只在初次调用一次
     */
    static {
        JedisPoolConfig config = new JedisPoolConfig();
        //最大连接数
        config.setMaxTotal(MAX_ACTIVE);
        //最多空闲实例
        config.setMaxIdle(MAX_IDLE);
        //超时时间
        config.setMaxWaitMillis(MAX_WAIT);
        /**
         * test on borrow
         * 如果连接池中存在空闲可用的连接实例，则需要连接时根据后进先出策略挑选一个连接实例，并激活返回给客户端。
         * 如果该值设为true，则会进行校验。若校验失败，该连接实例会被废弃，并继续检查下一个可用的连接实例，
         * 直到找到一个合法可用的连接实例或连接池中没有可用的连接实例。
         * 该值默认为false, test on borrow 对性能的损耗是比较高的。
         * 如果没有可用的空闲连接实例，则要看max total, 连接池是否达到exhausted和borrowMaxWaitMills参数，
         * 如果池中连接实例的数量少于max total, 则会创建一个新的实例，激活，校验并返回给调用者。
         * 如果校验失败，则会抛NoSuchElementException异常。
         * 在连接池没有可用的连接实例且不能新建一个实例时，如果getBlockWhenExhausted参数为true，则阻塞，
         * 直到超过borrowMaxWaitMillis参数设置的超时实际，如果是false，则直接抛出NoSuchElementException异常。
         * 激活是简单的一个选库的操作，校验是验证host和port的正确性并做一次ping的动作。
         * test on return
         * 如果设置了max idle参数，且池中的空闲实例数达到这个数值，返回的连接实例将被废弃。
         * 如果该值设为true, 连接实例返回给连接池前需要做校验，如果校验失败，则实例废弃。同样是ping
         * 否则连接将根据先进先出或后进先出的策略还给线程池。
         * test while idle 和 time between eviction runs millis
         * 这两个参数往往是配合使用的，即每隔多长时间验证一下空闲的连接实例是否有效。
         * 源码中EvictionTimer空闲连接回收器是通过ScheduledThreadPoolExecutor来做定时校验的
         *
         * 假如连接池中的连接被数据库关闭了，应用通过连接池获取连接时，可能会获取这些不可用的连接，
         * 且这些连接如果不被其他线程回收的话，它们就不会被连接池废弃，也不会重新创建，
         * test on borrow 设为true，则会在获取连接时进行测试，从而保证服务器的稳定，但很消耗性能
         * 或者使用test while idle, time between eviction runs millis来避免这个问题，
         * 不过这几个配置都是默认为false的
         */
        config.setTestOnBorrow(false);
        config.setTestOnReturn(false);
        config.setTestWhileIdle(false);
        // pool = new JedisPool(config, ADDR, PORT, 1000);
        pool = new JedisPool(config, ADDR, PORT, 1000, AUTH);
    }
    /**
     * 获取jedis实例
     * redis.clients.jedis.Jedis这个客户端实例不是线程安全的，所以需要自行保证线程安全
     * redis.clients.jedis.JedisPool连接池是线程安全的，
     * 或者说其提供的getResource()方法是线程安全的，所以在封装工具类的时候不需要再使用同步手段了。
     */
    protected static /*synchronized*/ Jedis getJedis() {
        Jedis jedis = null;
        try {
            jedis = pool.getResource();
        } catch (Exception e) {
            e.printStackTrace();
            if (jedis != null) {
                // pool.returnBrokenResource(jedis);
                jedis.close();
            }
        }
        return jedis;
    }
    /**
     * 释放jedis资源
     *
     * @param jedis
     * @param isBroken
     */
    protected static void closeResource(Jedis jedis, boolean isBroken) {
        try {
            /*
            if (isBroken) {
                pool.returnBrokenResource(jedis);
            } else {
                pool.returnResource(jedis);
            }*/
            jedis.close();
        } catch (Exception e) {

        }
    }
    /**
     *  是否存在key
     *
     * @param key
     */
    public static boolean existKey(String key) {
        Jedis jedis = null;
        boolean isBroken = false;
        try {
            jedis = getJedis();
            jedis.select(0);
            return jedis.exists(key);
        } catch (Exception e) {
            isBroken = true;
        } finally {
            closeResource(jedis, isBroken);
        }
        return false;
    }
    /**
     *  删除key
     *
     * @param key
     */
    public static void delKey(String key) {
        Jedis jedis = null;
        boolean isBroken = false;
        try {
            jedis = getJedis();
            jedis.select(0);
            jedis.del(key);
        } catch (Exception e) {
            isBroken = true;
        } finally {
            closeResource(jedis, isBroken);
        }
    }
    /**
     *  取得key的值
     *
     * @param key
     */
    public static String stringGet(String key) {
        Jedis jedis = null;
        boolean isBroken = false;
        String lastVal = null;
        try {
            jedis = getJedis();
            jedis.select(0);
            lastVal = jedis.get(key);
            // jedis.expire(key, expireTime);
        } catch (Exception e) {
            isBroken = true;
        } finally {
            closeResource(jedis, isBroken);
        }
        return lastVal;
    }
    /**
     *  添加string数据
     *
     * @param key
     * @param value
     */
    public static String stringSet(String key, String value) {
        Jedis jedis = null;
        boolean isBroken = false;
        String lastVal = null;
        try {
            jedis = getJedis();
            jedis.select(0);
            lastVal = jedis.set(key, value);
            // jedis.expire(key, expireTime); 这里为什么要设置过期时间
        } catch (Exception e) {
            e.printStackTrace();
            isBroken = true;
        } finally {
            closeResource(jedis, isBroken);
        }
        return lastVal;
    }
    /**
     *  添加hash数据
     *
     * @param key
     * @param field
     * @param value
     */
    public static void hashSet(String key, String field, String value) {
        boolean isBroken = false;
        Jedis jedis = null;
        try {
            jedis = getJedis();
            if (jedis != null) {
                jedis.select(0);
                jedis.hset(key, field, value);
                // jedis.expire(key, expireTime);
            }
        } catch (Exception e) {
            isBroken = true;
        } finally {
            closeResource(jedis, isBroken);
        }
    }

}
